package com.anecdote.ideaplugins.changesbar;

/**
 * Copyright 2006 Nathan Brown
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
import com.intellij.ide.scopeView.ScopeViewPane;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.actions.ShowMessageHistoryAction;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.ui.*;
import com.intellij.openapi.vcs.checkin.CheckinHandler;
import com.intellij.openapi.vcs.checkin.CheckinHandlerFactory;
import com.intellij.openapi.vcs.ui.Refreshable;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.wm.*;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.Alarm;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.*;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.*;
import javax.swing.text.BadLocationException;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.undo.UndoManager;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;

@SuppressWarnings({"UnresolvedPropertyKey", "SSBasedInspection"})
public class ChangesBarProjectComponent extends CheckinHandlerFactory
  implements ProjectComponent,
  ChangeListener
{
  public static final String CHANGES_BAR_COMMIT_COMMENT_ACTIONS = "CHANGES_BAR_COMMIT_COMMENT_ACTIONS";
  private final Project _project;
  private ChangesBar _changesBar;
  private StatusBar _statusBar;
  @Nullable
  private ChangeListManager _changeListManager;
  private static final Icon COMMIT_ICON = IconLoader.getIcon("/actions/commit.png");
  private static final Icon MESSAGE_HISTORY_ICON = IconLoader.getIcon("/runConfigurations/testOuput.png");
  private static final String CONFLICT_ARROW = ">!!<";
  private static final String DESCRIPTION_START = "Changelist holding the files that were originally" +
                                                  " modified in Changelist \"";
  private static final String DESCRIPTION_MIDDLE = "\" but were subsequently modified in Changelist";
  private static final String DESCRIPTION_END = ".\n\n(Autogenerated by " +
                                                "the ChangesBar plugin.)";
  private LocalChangeList _selectionChangeList;
  static final String CHANGES_TOOLWINDOW_NAME = "Changes";
  private final ShowMessageHistoryAction _messageHistoryAction = new ShowMessageHistoryAction();
  private ChangesListView _changesListView;
  private Container _changesListViewContainerPanel;
  private JSplitPane _changesListViewCommentViewSplit;
  private Container _changesListViewScrollPane;
  private boolean _commentEditorInstalled;
  private final TreeSelectionListener _changesListViewSelectionListener = new TreeSelectionListener()
  {
    public void valueChanged(TreeSelectionEvent e)
    {
      updateSelectionChangeList();
    }
  };
  private final PropertyChangeListener _changesListViewRendererChangeListener = new PropertyChangeListener()
  {
    public void propertyChange(PropertyChangeEvent evt)
    {
      if (evt.getNewValue() != null) {
        final TreeCellRenderer treeCellRenderer = (TreeCellRenderer)evt.getNewValue();
        changesListViewRendererChanged(treeCellRenderer);
      }
    }
  };
  private boolean _settingChangesListViewRenderer;
  private ProjectLevelVcsManager _vcsManager;
  private boolean _committing;
  private CheckinHandler _checkinHandler = new CheckinHandler()
  {
    @Override
    public ReturnResult beforeCheckin()
    {
      _committing = true;
      return ReturnResult.COMMIT;
    }

    @Override
    public void checkinFailed(List<VcsException> vcsExceptions)
    {
      _committing = false;
    }

    @Override
    public void checkinSuccessful()
    {
      _committing = false;
    }

    @Override
    public ReturnResult beforeCheckin(@Nullable CommitExecutor commitExecutor)
    {
      return beforeCheckin();
    }
  };
  private Runnable _queuedFileChangeHandler;
  private final ArrayList<VirtualFile> _changedFiles = new ArrayList<VirtualFile>();
  private Alarm _alarm;
  private LocalFileSystemListener _localFileSystemListener;
  private ChangeListAdapter _changeListListener;
  private CommentPanel _commentPanel;

  public ChangesBarProjectComponent(Project project)
  {
    _project = project;
    _alarm = new Alarm(com.intellij.util.Alarm.ThreadToUse.SWING_THREAD, project);
  }

  public void initComponent()
  {
    _vcsManager = ProjectLevelVcsManager.getInstance(_project);
    _vcsManager.registerCheckinHandlerFactory(ChangesBarProjectComponent.this);
  }

  public void disposeComponent()
  {
    _vcsManager.unregisterCheckinHandlerFactory(ChangesBarProjectComponent.this);
  }

  @NotNull
  public String getComponentName()
  {
    return "ChangeListProjectComponent";
  }

  public void projectOpened()
  {
    // called when project is opened
    if (!_project.isDisposed()) {
      StartupManager startupManager = StartupManager.getInstance(_project);
      startupManager.runWhenProjectIsInitialized(new Runnable()
      {
        public void run()
        {
          if (!_project.isDisposed()) {
            ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(_project);
            vcsManager.updateActiveVcss();
            AbstractVcs[] vcses = vcsManager.getAllActiveVcss();
            if (vcses != null && vcses.length > 0) {
              final WindowManager windowManager = WindowManager.getInstance();
              _statusBar = windowManager.getStatusBar(_project);
              _changesBar = new ChangesBar(ChangesBarProjectComponent.this);
              _statusBar.addCustomIndicationComponent(_changesBar);
              final ChangeListManager changeListManager = ChangeListManager.getInstance(_project);
              setChangeListManager(changeListManager);
              LocalFileSystem.getInstance().addVirtualFileListener(getLocalFileSystemListener());
              ChangesBarApplicationComponent.addChangeListener(ChangesBarProjectComponent.this);
              stateChanged(null);
            }
          }
        }
      });
    }
  }

//  private void queueStateChanged()
//  {
//    ToolWindowManager.getInstance(_project).invokeLater(new Runnable()
//    {
//      public void run()
//      {
//        stateChanged(null);
//      }
//    });
//  }

  private void installCommentEditor()
  {
//    System.out.println("Installing Commit Comment Editor in Changes Toolwindow for Project " + _project.getName());
    final ToolWindow window = ToolWindowManager.getInstance(_project).getToolWindow(CHANGES_TOOLWINDOW_NAME);
    if (window != null) {
      final ChangesListView listView = findChildOfClass(ChangesListView.class, window.getComponent());
      setChangesListView(listView);
    }
  }

  private void deinstallCommentEditor()
  {
    setChangesListView(null);
  }

  private void setChangesListView(ChangesListView listView)
  {
    if (listView == _changesListView) {
      return;
    }
    if (_changesListView != null) {
      _changesListView.removeTreeSelectionListener(_changesListViewSelectionListener);
      _changesListView.removePropertyChangeListener(JTree.CELL_RENDERER_PROPERTY,
                                                    _changesListViewRendererChangeListener);
      if (_commentEditorInstalled) {
        _changesListViewContainerPanel.remove(_changesListViewCommentViewSplit);
        _changesListViewContainerPanel.add(_changesListViewScrollPane, BorderLayout.CENTER);
        _changesListViewContainerPanel.validate();
        _changesListViewCommentViewSplit = null;
        _changesListViewScrollPane = null;
        _changesListViewContainerPanel = null;
        _commentEditorInstalled = false;
      }
    }
    _changesListView = listView;
    if (_changesListView == null) {
      return;
    }
    _changesListView.addPropertyChangeListener(JTree.CELL_RENDERER_PROPERTY,
                                               _changesListViewRendererChangeListener);
    final TreeCellRenderer cellRenderer = _changesListView.getCellRenderer();
    changesListViewRendererChanged(cellRenderer);
    _changesListView.addTreeSelectionListener(_changesListViewSelectionListener);
    _changesListViewScrollPane = _changesListView.getParent().getParent();
    if (_changesListViewScrollPane instanceof JScrollPane) {
      _changesListViewContainerPanel = _changesListViewScrollPane.getParent();
      if (_changesListViewContainerPanel.getLayout() instanceof BorderLayout) {
        _changesListViewContainerPanel.remove(_changesListViewScrollPane);

        _commentPanel = new CommentPanel();

        _changesListViewCommentViewSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, _changesListViewScrollPane,
                                                          _commentPanel);
        _changesListViewContainerPanel.add(_changesListViewCommentViewSplit, BorderLayout.CENTER);
        _changesListViewContainerPanel.validate();
        _changesListViewCommentViewSplit.setDividerLocation(0.5D);
        _changesListViewCommentViewSplit.setResizeWeight(0.5D);

        _commentEditorInstalled = true;
        updateCommentEditor(_selectionChangeList);
      } else {
        System.err.println(
          "ChangesBar - View structure not as expected! (Layout) - Aborting Comment Editor Install.");
      }
    } else {
      System.err.println(
        "ChangesBar - View structure not as expected! (JScrollPane) - Aborting Comment Editor Install.");
    }
  }

  private void commitComment()
  {
    if (!(_selectionChangeList != null)) {
      return;
    }
    final LocalChangeList list = _selectionChangeList;
//    list.setName(list.getName());
    list.setComment(_commentPanel._commentEditor.getText());
  }

  private void changesListViewRendererChanged(TreeCellRenderer treeCellRenderer)
  {
    if (_settingChangesListViewRenderer) {
      return;
    }
    _settingChangesListViewRenderer = true;
    try {
      _changesListView.setCellRenderer(new CustomChangeListNodeRenderer(treeCellRenderer));
    } finally {
      _settingChangesListViewRenderer = false;
    }
  }

  private void updateSelectionChangeList()
  {
    final TreePath[] treePaths = _changesListView.getSelectionPaths();
    ChangesBrowserChangeListNode changeListNode = null;
    if (treePaths != null) {
      for (final TreePath treePath : treePaths) {
        final Object node = treePath.getPathComponent(1);
        if (!(node instanceof ChangesBrowserChangeListNode)) {
          continue;
        }
        if (changeListNode == null) {
          changeListNode = (ChangesBrowserChangeListNode)node;
        } else if (!changeListNode.equals(node)) {
          setSelectionChangeList(null);
          return;
        }
      }
    }
    setSelectionChangeList(changeListNode != null ? (LocalChangeList)changeListNode.getUserObject() : null);
  }

  private static <T> T findChildOfClass(Class<T> cls, Component component)
  {
    if (cls.isInstance(component)) {
      return cls.cast(component);
    }
    if (!(component instanceof Container)) {
      return null;
    }
    for (final Component child : ((Container)component).getComponents()) {
      final T childOfClass = findChildOfClass(cls, child);
      if (childOfClass != null) {
        return childOfClass;
      }
    }
    return null;
  }

  private void setChangeListManager(ChangeListManager changeListManager)
  {
    if (_changeListManager == changeListManager) {
      return;
    }
    if (_changeListManager != null) {
      _changeListManager.removeChangeListListener(getChangeListListener());
    }
    _changeListManager = changeListManager;
    if (_changeListManager != null) {
      _changeListManager.addChangeListListener(getChangeListListener());
      scanForEmptyConflictChangeLists();
    }
    _changesBar.setChangeListManager(_changeListManager);
  }

  private void scanForEmptyConflictChangeLists()
  {
    if (_changeListManager != null) {
      List<LocalChangeList> changeLists = _changeListManager.getChangeLists();
      LocalChangeList[] changeListArray = changeLists.toArray(new LocalChangeList[changeLists.size()]);
      for (LocalChangeList list : changeListArray) {
        if (isConflictChangeList(list) && list.getChanges().isEmpty()) {
          _changeListManager.removeChangeList(list);
        }
      }
    }
  }

  public void projectClosed()
  {
    ChangesBarApplicationComponent.removeChangeListener(ChangesBarProjectComponent.this);
    // called when project is being closed
    setChangeListManager(null);
    _statusBar = null;
    _changesBar = null;
    LocalFileSystem.getInstance().removeVirtualFileListener(getLocalFileSystemListener());
  }

  private void fileChanged(VirtualFile file, FileChangeType changeType)
  {
    if (!ChangesBarApplicationComponent.isWatchForCollisions() ||
        changeType == FileChangeType.Copied ||
        ProjectLevelVcsManager.getInstance(_project).isBackgroundVcsOperationRunning() ||
        _committing) {
      return;
    }
    synchronized (_changedFiles) {
      _changedFiles.add(file);
      if (_queuedFileChangeHandler == null) {
        _queuedFileChangeHandler = new Runnable()
        {
          public void run()
          {
            synchronized (_changedFiles) {
              for (VirtualFile changedFile : _changedFiles) {
                fileChanged(changedFile);
              }
              _changedFiles.clear();
              _changedFiles.trimToSize();
              _queuedFileChangeHandler = null;
            }
          }
        };
        SwingUtilities.invokeLater(_queuedFileChangeHandler);
      }
    }
  }

  private void fileChanged(VirtualFile file)
  {
    if (_changeListManager != null) {
      final Change change = _changeListManager.getChange(file);
      final LocalChangeList changeList = change != null ? _changeListManager.getChangeList(change) : null;
      final LocalChangeList defaultChangeList = _changeListManager.getDefaultChangeList();
      if (defaultChangeList == null || changeList == null || changeList.equals(defaultChangeList)) {
        return;
      }
      final String originalChangeListName = changeList.getName();
      final String newChangeListName = defaultChangeList.getName();
      final List<String> collisionNames = getCollidingChangeListNames(originalChangeListName);
      if (collisionNames != null && collisionNames.contains(newChangeListName)) {
        // already in the set - abort
        return;
      }
      final String collisionChangeListName = originalChangeListName + CONFLICT_ARROW + newChangeListName;
      LocalChangeList list = _changeListManager.findChangeList(collisionChangeListName);
      if (list == null) {
        String descriptionString;
        if (collisionNames != null) {
          collisionNames.add(newChangeListName);
          descriptionString = getCollisionListDescription(collisionNames);
        } else {
          final StringBuilder description = new StringBuilder(DESCRIPTION_START);
          description.append(originalChangeListName);
          description.append(DESCRIPTION_MIDDLE);
          description.append(" \"").append(newChangeListName).append('\"');
          description.append(DESCRIPTION_END);
          descriptionString = description.toString();
        }
        list = _changeListManager.addChangeList(collisionChangeListName,
                                                descriptionString);
        list.setReadOnly(true);
      }
      _changeListManager.moveChangesTo(list, new Change[]{change});
      _changesBar.setWarningVisible(list);
    }
  }

  private static String getCollisionListDescription(List<String> collisionNames)
  {
    final StringBuilder description = new StringBuilder(DESCRIPTION_START);
    description.append(collisionNames.get(0));
    description.append(DESCRIPTION_MIDDLE);
    description.append("s ");
    for (int i = 1; i < collisionNames.size() - 1; i++) {
      final String subName = collisionNames.get(i);
      description.append('\"');
      description.append(subName);
      description.append('\"');
      if (i < collisionNames.size() - 2) {
        description.append(", ");
      }
    }
    description.append(" and \"").append(collisionNames.get(collisionNames.size() - 1)).append('\"');
    description.append(DESCRIPTION_END);
    return description.toString();
  }

  private void updateCommentEditor(ChangeList list)
  {
    _commentPanel.updateCommentEditor(list);
  }

  private static String getCollisionListName(List<String> collisionNames)
  {
    //noinspection StringBufferWithoutInitialCapacity
    final StringBuilder newName = new StringBuilder();
    for (int i = 0; i < collisionNames.size(); i++) {
      final String subName = collisionNames.get(i);
      newName.append(subName);
      if (i < collisionNames.size() - 1) {
        newName.append(CONFLICT_ARROW);
      }
    }
    return newName.toString();
  }

  private void removeChangeListIfNotValid(final ChangeList changeList)
  {
    SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        if (isConflictChangeList(changeList) && changeList.getChanges().isEmpty()) {
          _changeListManager.removeChangeList((LocalChangeList)changeList);
        }
      }
    });
  }

  private void openFilesIfNecessary(ChangeList newDefaultList, boolean focusEditors)
  {
    if (ChangesBarApplicationComponent.isOpenFilesWhenSwitchingChangelist()) {
      for (Change change : newDefaultList.getChanges()) {
        ContentRevision contentRevision = change.getBeforeRevision();
        if (contentRevision != null) {
          FilePath filePath = contentRevision.getFile();
          FileEditorManager fileEditorManager = FileEditorManager.getInstance(_project);
          fileEditorManager.openFile(filePath.getVirtualFile(), focusEditors);
        }
      }
    }
  }

  private boolean closeFilesIfNecessary(ChangeList newDefaultList)
  {
    boolean result = false;
    if (ChangesBarApplicationComponent.isCloseFilesWhenSwitchingChangelist()) {
      FileEditorManager fileEditorManager = FileEditorManager.getInstance(_project);
      VirtualFile[] files = fileEditorManager.getOpenFiles();
      String newListName = newDefaultList.getName();
      for (VirtualFile file : files) {
        Change change = _changeListManager.getChange(file);
        if (change != null) {
          LocalChangeList changeList = _changeListManager.getChangeList(change);
          if (changeList != null) {
            if (!changeList.getName().equals(newListName)) {
              if (!result && Arrays.asList(fileEditorManager.getSelectedFiles()).contains(file)) {
                result = true;
              }
              fileEditorManager.closeFile(file);
            }
          }
        }
      }
    }
    return result;
  }

  private void refreshScopeViewIfNecessary()
  {
    if (ChangesBarApplicationComponent.isAddChangelistScope()) {
      SwingUtilities.invokeLater(new Runnable()
      {
        public void run()
        {
          if (!_project.isDisposed()) {
            final ProjectView view = ProjectView.getInstance(_project);
            if (isChangelistScopeShown(view)) {
              view.refresh();
              if (ChangesBarApplicationComponent.isExpandChangelistScope()) {
                TreeUtil.expandAll(view.getCurrentProjectViewPane().getTree());
              }
            }
          }
        }
      });
    }
  }

  private static boolean isChangelistScopeShown(@NotNull final ProjectView view)
  {
    final AbstractProjectViewPane pane = view.getCurrentProjectViewPane();
    return pane != null && ScopeViewPane.ID.equals(pane.getId()) && ChangesScopeProvider.SCOPE_NAME.equals(
      pane.getSubId());
  }

  static boolean isConflictChangeList(ChangeList changeList)
  {
    return changeList.getName().contains(CONFLICT_ARROW);
  }

  private static List<String> getCollidingChangeListNames(String changeListCollisionName)
  {
    List<String> result = null;
    if (!changeListCollisionName.contains(CONFLICT_ARROW)) {
      return result;
    }
    final StringTokenizer tokenizer = new StringTokenizer(changeListCollisionName, CONFLICT_ARROW);
    result = new ArrayList<String>();
    while (tokenizer.hasMoreElements()) {
      final String s = (String)tokenizer.nextElement();
      result.add(s);
    }
    return result;
  }

  private void setSelectionChangeList(LocalChangeList selectionChangeList)
  {
    if (selectionChangeList != null) {
      commitComment();
      selectionChangeList = _changeListManager.findChangeList(selectionChangeList.getName());
    }
    _selectionChangeList = selectionChangeList;
    updateCommentEditor(_selectionChangeList);
  }

  public void stateChanged(ChangeEvent e)
  {
    if (_commentEditorInstalled == ChangesBarApplicationComponent.isAddCommentEditor()) {
      return;
    }
    if (ChangesBarApplicationComponent.isAddCommentEditor()) {
      installCommentEditor();
    } else {
      deinstallCommentEditor();
    }
  }

  public Project getProject()
  {
    return _project;
  }

  public ChangesListView getChangesListView()
  {
    return _changesListView;
  }

  @NotNull
  public CheckinHandler createHandler(CheckinProjectPanel checkinProjectPanel)
  {
    return _checkinHandler;
  }

  public VirtualFileListener getLocalFileSystemListener()
  {
    if (_localFileSystemListener == null) {
      _localFileSystemListener = new LocalFileSystemListener();
    }
    return _localFileSystemListener;
  }

  public ChangeListListener getChangeListListener()
  {
    if (_changeListListener == null) {
      _changeListListener = new ChangeListAdapter();
    }
    return _changeListListener;
  }

  private static class SimpleDataContext implements DataContext
  {
    private final String _id;
    private final Object _data;

    private SimpleDataContext(String id, Object data)
    {
      _id = id;
      _data = data;
    }

    @Nullable
    public Object getData(@NonNls String dataId)
    {
      if (dataId.equals(_id)) {
        return _data;
      }
      return null;
    }
  }

  private static class CustomChangeListNodeRenderer implements TreeCellRenderer
  {
    private final TreeCellRenderer _cellRenderer;

    private CustomChangeListNodeRenderer(TreeCellRenderer cellRenderer)
    {
      _cellRenderer = cellRenderer;
    }

    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
                                                  boolean leaf, int row, boolean hasFocus)
    {
      final Component component = _cellRenderer.getTreeCellRendererComponent(tree, value, selected,
                                                                             expanded, leaf, row,
                                                                             hasFocus);
      if (!(component instanceof SimpleColoredComponent)) {
        return component;
      }
      final SimpleColoredComponent cellRendererComponent =
        (SimpleColoredComponent)component;
      final ChangesBrowserNode node = (ChangesBrowserNode)value;
      if (!(node.getUserObject() instanceof ChangeList)) {
        return component;
      }
      final ChangeList changeList = (ChangeList)node.getUserObject();
      if (!isConflictChangeList(changeList)) {
        return component;
      }
      if (!selected) {
        cellRendererComponent.clear();
        cellRendererComponent.append(changeList.getName(), SimpleTextAttributes.ERROR_ATTRIBUTES);
        appendNodeCount(cellRendererComponent, node);
      }
      cellRendererComponent.setIcon(ChangesBar.WARNING_ICON);
      return component;
    }
  }

  private static void appendNodeCount(SimpleColoredComponent component, ChangesBrowserNode node)
  {
    final int i = node.getCount();
    final int j = node.getDirectoryCount();
    if (j == 0) {
      component.append(new StringBuilder(" ").append(VcsBundle.message("changes.nodetitle.changecount",
                                                                       i)).toString(),
                       SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
    } else if (i == 0 && j > 0) {
      component.append(new StringBuilder(" ").append(VcsBundle.message(
        "changes.nodetitle.directory.changecount", j)).toString(),
                       SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
    } else {
      component.append(new StringBuilder(" ").append(VcsBundle.message(
        "changes.nodetitle.directory.file.changecount", j, i)).toString(), SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
    }
  }

  private abstract static class LocalisedAction extends AbstractAction
  {
    private LocalisedAction(String id, String iconName)
    {
      super("", iconName != null ? IconLoader.getIcon(iconName) : null);
      String text = ActionsBundle.actionText(id);
      Presentation presentation = new Presentation();
      presentation.setText(text, true);
      putValue(Action.NAME, presentation.getText());
      putValue(Action.MNEMONIC_KEY, presentation.getMnemonic());
      putValue(Action.SHORT_DESCRIPTION, ActionsBundle.actionDescription(id));
    }
  }

  private static class LocalisedActionWrapper extends LocalisedAction
  {
    private Action _delegate;

    private LocalisedActionWrapper(String id, String iconName, Action delegate)
    {
      super(id, iconName);
      _delegate = delegate;
    }

    public void update()
    {
      setEnabled(_delegate != null && _delegate.isEnabled());
    }

    public void actionPerformed(ActionEvent e)
    {
      if (_delegate != null) {
        _delegate.actionPerformed(getDelegatedActionEvent(e));
      }
    }

    protected ActionEvent getDelegatedActionEvent(ActionEvent e)
    {
      return e;
    }
  }

  private class LocalFileSystemListener implements VirtualFileListener
  {
    public void contentsChanged(VirtualFileEvent event)
    {
      fileChanged(event.getFile(), FileChangeType.Modified);
    }

    public void fileDeleted(VirtualFileEvent event)
    {
      fileChanged(event.getFile(), FileChangeType.Deleted);
    }

    public void fileMoved(VirtualFileMoveEvent event)
    {
      fileChanged(event.getFile(), FileChangeType.Moved);
    }

    public void fileCopied(VirtualFileCopyEvent event)
    {
      fileChanged(event.getFile(), FileChangeType.Copied);
    }

    public void propertyChanged(VirtualFilePropertyEvent event)
    {
      if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
        fileChanged(event.getFile(), FileChangeType.Renamed);
      }
    }

    public void beforePropertyChange(VirtualFilePropertyEvent event)
    {
      // no op
    }

    public void beforeContentsChange(VirtualFileEvent event)
    {
      // no op
    }

    public void beforeFileDeletion(VirtualFileEvent event)
    {
      // no op
    }

    public void beforeFileMovement(VirtualFileMoveEvent event)
    {
      // no op
    }

    public void fileCreated(VirtualFileEvent event)
    {
      // no op
    }
  }

  private class ChangeListAdapter implements ChangeListListener
  {
    public void changeListAdded(ChangeList list)
    {
      // no op
    }

    public void changesRemoved(Collection<Change> changes, ChangeList fromList)
    {
      refreshScopeViewIfNecessary();

      if (changes.isEmpty()) {
        return;
      }
      if (isConflictChangeList(fromList)) {
        _changesBar.setWarningVisible(null);
      }
      removeChangeListIfNotValid(fromList);
    }

    public void changeListRemoved(ChangeList list)
    {
      if (isConflictChangeList(list)) {
        _changesBar.setWarningVisible(null);
      }
    }

    public void changeListRenamed(ChangeList renamedList, String oldName)
    {
      if (isConflictChangeList(renamedList)) {
        return;
      }
      String newName = renamedList.getName();
      // rename any autogenerated change lists accordingly
      final List<LocalChangeList> localChangeLists = _changeListManager.getChangeLists();
      for (final LocalChangeList changeList : localChangeLists) {
        if (changeList == renamedList) {
          continue;
        }
        final String name = changeList.getName();
        final List<String> list = getCollidingChangeListNames(name);
        if (list == null) {
          continue;
        }
        for (int i = 0; i < list.size(); i++) {
          final String s = list.get(i);
          if (s.equals(oldName)) {
            list.set(i, newName);
          }
        }
        SwingUtilities.invokeLater(new Runnable()
        {
          public void run()
          {
            changeList.setName(getCollisionListName(list));
            changeList.setComment(getCollisionListDescription(list));
          }
        });
      }
    }

    public void changeListCommentChanged(final ChangeList list, String oldComment)
    {
      SwingUtilities.invokeLater(new Runnable()
      {
        public void run()
        {
          if (list.equals(_selectionChangeList) && _commentPanel._updatingCommentRefCount == 0) {
            updateCommentEditor(list);
          }
        }
      });
    }

    public void changesMoved(Collection<Change> changes, ChangeList fromList, ChangeList toList)
    {
      refreshScopeViewIfNecessary();

      if (changes.isEmpty()) {
        return;
      }
      if (isConflictChangeList(fromList)) {
        _changesBar.setWarningVisible(null);
      }
      removeChangeListIfNotValid(fromList);
    }

    public void changeListChanged(ChangeList list)
    {
      removeChangeListIfNotValid(list);
    }

    public void defaultListChanged(final ChangeList oldDefaultList, final ChangeList newDefaultList)
    {
      refreshScopeViewIfNecessary();
      SwingUtilities.invokeLater(new Runnable()
      {
        public void run()
        {
          boolean activeClosed = closeFilesIfNecessary(newDefaultList);
          openFilesIfNecessary(newDefaultList,
                               activeClosed || !ChangesBarApplicationComponent.isOpenFilesInBackground());
        }
      });
      // revert - can't have autogen lists default
      if (isConflictChangeList(newDefaultList)) {
        SwingUtilities.invokeLater(new Runnable()
        {
          public void run()
          {
            _changeListManager.setDefaultChangeList((LocalChangeList)oldDefaultList);
          }
        }
        );
      }
    }

    public void unchangedFileStatusChanged()
    {
      refreshScopeViewIfNecessary();
      // to do : work out what this means
      //    System.out.println("ChangesBarProjectComponent.unchangedFileStatusChanged()");
    }

    public void changeListUpdateDone()
    {
      refreshScopeViewIfNecessary();
      scanForEmptyConflictChangeLists();
    }
  }

  private class CommentPanel extends JPanel implements DataProvider
  {
    private final JTextArea _commentEditor = new JTextArea();
    private UndoManager _commentEditorUndoManager = new UndoManager();
    private RedoAction _commentEditorRedoAction = new RedoAction();
    private UndoAction _commentEditorUndoAction = new UndoAction();
    private ClipboardAction _commentEditorCutAction =
      new ClipboardAction(IdeActions.ACTION_EDITOR_CUT, "/actions/menu-cut.png",
                          TransferHandler.getCutAction());
    private ClipboardAction _commentEditorCopyAction =
      new ClipboardAction(IdeActions.ACTION_EDITOR_COPY, "/actions/menu-copy.png",
                          TransferHandler.getCutAction());
    private PasteAction _commentEditorPasteAction = new PasteAction();
    private CommentEditorSelectionAction _commentEditorDeleteAction =
      new CommentEditorSelectionAction(IdeActions.ACTION_EDITOR_DELETE, null, new AbstractAction()
      {
        @SuppressWarnings({"CallToPrintStackTrace"})
        public void actionPerformed(ActionEvent e)
        {
          try {
            int start = _commentEditor.getSelectionStart();
            int end = _commentEditor.getSelectionEnd();
            _commentEditor.getDocument().remove(start, end - start);
          } catch (BadLocationException e1) {
            e1.printStackTrace();
          }
        }
      });
    private Action _commentEditorSelectAllAction = new LocalisedAction("$SelectAll", null)
    {
      public void actionPerformed(ActionEvent e)
      {
        _commentEditor.selectAll();
      }
    };
    private JPopupMenu _commentEditorPopupMenu = new JPopupMenu();
    private final TitledBorder _commentEditorBorder = new TitledBorder("Comment");
    private int _updatingCommentRefCount;
    private final JButton _commitButton = new JButton(null, COMMIT_ICON);
    private final JButton _messageHistoryButton = new JButton(null, MESSAGE_HISTORY_ICON);

    {
      _messageHistoryButton.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          _updatingCommentRefCount++;
          try {
            final CheckinProjectPanelStub checkinProjectPanel = new CheckinProjectPanelStub()
            {
              @Override
              public Project getProject()
              {
                return _project;
              }

              @Override
              public void setCommitMessage(String currentDescription)
              {
                setCommentEditorText(currentDescription);
              }
            };
            _messageHistoryAction.actionPerformed(
              new AnActionEvent(null,
                                new SimpleDataContext(Refreshable.PANEL, checkinProjectPanel),
                                "ChangesBar", _messageHistoryAction.getTemplatePresentation(), null, 0)
            );
            _commentEditor.selectAll();
            _commentEditor.requestFocusInWindow();
          } finally {
            _updatingCommentRefCount--;
          }
        }
      });
      _commitButton.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          CommitChangeListDialog.commitChanges(_project, _selectionChangeList.getChanges(), _selectionChangeList,
                                               _changeListManager.getRegisteredExecutors(), true,
                                               _commentEditor.getText());
        }
      });
    }

    public CommentPanel()
    {
      super(new BorderLayout());
      final JScrollPane commentScrollPane = new JScrollPane(_commentEditor,
                                                            ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                                                            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
      commentScrollPane.getViewport().setBackground(_commentEditor.getBackground());
      final JPanel borderPanel = new JPanel(new BorderLayout());
      borderPanel.add(commentScrollPane, BorderLayout.CENTER);
      borderPanel.setBorder(_commentEditorBorder);
      add(borderPanel, BorderLayout.CENTER);

      final JPanel buttonPanel = new JPanel();
      buttonPanel.setLayout(new BorderLayout());
      buttonPanel.add(_commitButton, BorderLayout.EAST);
      ActionGroup commitCommentActions =
        (ActionGroup)ActionManager.getInstance().getAction(CHANGES_BAR_COMMIT_COMMENT_ACTIONS);
      ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(
        "CHANGES_BAR_COMMIT_COMMENT_TOOLBAR",
        commitCommentActions,
        true);
      actionToolbar.setTargetComponent(this);
      buttonPanel.add(actionToolbar.getComponent(), BorderLayout.CENTER);
      buttonPanel.add(_messageHistoryButton, BorderLayout.WEST);
      final Presentation presentation = new Presentation();
      //noinspection UnresolvedPropertyKey
      String text = ActionsBundle.message("action.ChangesView.Commit.text");
      if (text.startsWith("!")) {
        text = "Comm_it Changes";
      }
      presentation.setText(text + "...", true);
      _commitButton.setText(presentation.getText());
      _commitButton.setMnemonic(presentation.getMnemonic());
      _commitButton.putClientProperty("Quaqua.Button.style", "placard");

      presentation.setText("_Message History...", true);
      _messageHistoryButton.setText(presentation.getText());
      _messageHistoryButton.setMnemonic(presentation.getMnemonic());
      borderPanel.add(buttonPanel, BorderLayout.SOUTH);
      _messageHistoryButton.putClientProperty("Quaqua.Button.style", "placard");

      _commentEditor.setWrapStyleWord(true);
      _commentEditor.setLineWrap(true);
      _commentEditor.addKeyListener(new KeyAdapter()
      {
        @Override
        public void keyPressed(KeyEvent e)
        {
          // to do - maybe get these from keymap?
          if (e.getKeyCode() == KeyEvent.VK_ENTER && e.isControlDown()) {
            commitComment();
            _commitButton.doClick();
            e.consume();
          } else if (e.getKeyCode() == KeyEvent.VK_Z) {
            if (e.isControlDown()) {
              ActionEvent ae = new ActionEvent(_commentEditor, 0, "", e.getModifiers());
              if (e.isShiftDown()) {
                _commentEditorRedoAction.actionPerformed(ae);
              } else {
                _commentEditorUndoAction.actionPerformed(ae);
              }
              e.consume();
            }
          }
        }
      });
//        _commentEditor.addAncestorListener(new AncestorListener()
//        {
//          public void ancestorAdded(AncestorEvent event)
//          {
//            System.out.println("ChangesBarProjectComponent.ancestorAdded : event = " + event);
//          }
//
//          public void ancestorRemoved(AncestorEvent event)
//          {
//            System.out.println("ChangesBarProjectComponent.ancestorRemoved : event = " + event);
//          }
//
//          public void ancestorMoved(AncestorEvent event)
//          {
//            System.out.println("ChangesBarProjectComponent.ancestorMoved : event = " + event);
//          }
//        });
      _commentEditor.addFocusListener(new FocusListener()
      {
        public void focusGained(FocusEvent e)
        {
        }

        public void focusLost(FocusEvent e)
        {
          commitComment();
        }
      });
      _commentEditor.getCaret().addChangeListener(new ChangeListener()
      {
        public void stateChanged(ChangeEvent e)
        {
          updateCommentEditorSelectionActions();
        }
      });
      _commentEditor.getDocument().addUndoableEditListener(new UndoableEditListener()
      {
        public void undoableEditHappened(UndoableEditEvent e)
        {
          _commentEditorUndoManager.addEdit(e.getEdit());
          updateCommentEditorUndoRedoActions();
        }
      });
      _commentEditorPopupMenu.removeAll();
      _commentEditorPopupMenu.add(_commentEditorUndoAction);
      _commentEditorPopupMenu.add(_commentEditorRedoAction);
      _commentEditorPopupMenu.addSeparator();
      _commentEditorPopupMenu.add(_commentEditorCutAction);
      _commentEditorPopupMenu.add(_commentEditorCopyAction);
      _commentEditorPopupMenu.add(_commentEditorPasteAction);
      _commentEditorPopupMenu.add(_commentEditorDeleteAction);
      _commentEditorPopupMenu.addSeparator();
      _commentEditorPopupMenu.add(_commentEditorSelectAllAction);
      _commentEditor.setComponentPopupMenu(_commentEditorPopupMenu);
    }

    private void updateCommentEditorSelectionActions()
    {
      _commentEditorCopyAction.update();
      _commentEditorCutAction.update();
      _commentEditorDeleteAction.update();
    }

    private void updateCommentEditorUndoRedoActions()
    {
      _commentEditorUndoAction.update();
      _commentEditorRedoAction.update();
    }

    public void updateCommentEditor(ChangeList list)
    {
      if (_updatingCommentRefCount == 0) {
        _updatingCommentRefCount++;
        try {
          setCommentEditorText(list != null ? list.getComment() : "No list selected");
          _commentEditorBorder.setTitle(list != null ? "Comment for \"" + list.getName() + '\"' : "Comment");
          _commentEditor.setEnabled(list instanceof LocalChangeList);
          _changesListViewContainerPanel.repaint();
          _commitButton.setEnabled(list != null);
          _messageHistoryButton.setEnabled(list != null);
        } finally {
          _updatingCommentRefCount--;
        }
      }
    }

    private void setCommentEditorText(String currentDescription)
    {
      if (!_commentEditor.getText().equals(currentDescription)) {
        _commentEditor.setText(currentDescription);
        _commentEditorUndoManager.discardAllEdits();
        updateCommentEditorUndoRedoActions();
        updateCommentEditorSelectionActions();
      }
    }

    public Object getData(@NonNls String dataId)
    {
      if ("CHANGES_BAR_COMMENT_EDITOR".equals(dataId)) {
        return _commentEditor;
      }
      if ("SELECTED_CHANGE_LIST".equals(dataId)) {
        return _selectionChangeList;
      }
      if (DataKeys.PROJECT.getName().equals(dataId)) {
        return _project;
      }
      return null;
    }

    private class RedoAction extends LocalisedAction
    {
      private RedoAction()
      {
        super("$Redo", "/actions/redo.png");
      }

      public void actionPerformed(ActionEvent e)
      {
        if (_commentEditorUndoManager.canRedo()) {
          _commentEditorUndoManager.redo();
        }
        updateCommentEditorUndoRedoActions();
      }

      public void update()
      {
        setEnabled(_commentEditorUndoManager.canRedo());
      }
    }

    private class UndoAction extends LocalisedAction
    {
      private UndoAction()
      {
        super("$Undo", "/actions/undo.png");
      }

      public void actionPerformed(ActionEvent e)
      {
        if (_commentEditorUndoManager.canUndo()) {
          _commentEditorUndoManager.undo();
        }
        updateCommentEditorUndoRedoActions();
      }

      public void update()
      {
        setEnabled(_commentEditorUndoManager.canUndo());
      }
    }

    private class CommentEditorSelectionAction extends LocalisedActionWrapper
    {
      private CommentEditorSelectionAction(String id, String iconName, Action delegate)
      {
        super(id, iconName, delegate);
      }

      @Override
      public void update()
      {
        super.update();
        setEnabled(isEnabled() && _commentEditor.getSelectionStart() != _commentEditor.getSelectionEnd());
      }
    }

    private class ClipboardAction extends CommentEditorSelectionAction
    {
      public ClipboardAction(String id, String iconName, Action action)
      {
        super(id, iconName, action);
      }

      @Override
      protected ActionEvent getDelegatedActionEvent(ActionEvent e)
      {
        return new ActionEvent(_commentEditor, e.getID(), e.getActionCommand(), e.getModifiers());
      }
    }

    private class PasteAction extends LocalisedActionWrapper
    {
      public PasteAction()
      {
        super(IdeActions.ACTION_EDITOR_PASTE, "/actions/menu-paste.png", TransferHandler.getPasteAction());
      }

      @Override
      protected ActionEvent getDelegatedActionEvent(ActionEvent e)
      {
        return new ActionEvent(_commentEditor, e.getID(), e.getActionCommand(), e.getModifiers());
      }
    }
  }
}
